Chapter 15
MFC ActiveX Control Containers

by Gene Olafsen

In This Chapter

  Active Document Container 562
  OLE Controls 571

This chapter describes the function and implementation of OLE containers. In previous chapters, you built various COM servers: active document, automation, and controls. With the exception of automation servers, there is still a need to explore client-side issues for active documents and ActiveX control containment.

Active Document Container

An active document container is an application that supports the in-place activation of an active document server. The two most popular active document containers are the Microsoft Office Binder and Internet Explorer. Although the Office Binder application has been shipping for a number of years, unless your line of business calls for its use, you probably ignore this application when it is installed with Office. The purpose of the binder program is to organize “documents” of various types: Word, Excel, and PowerPoint, by project. Thus, when you want to work on the “TideWater” account, you simply open the binder project, and the associated files appear in a pane that anchors to the left of the application while Word, Excel, and so on, activate in the remaining client area.

Likewise, Internet Explorer is an active document container. You can demonstrate this for yourself by entering the path and name of a file in the Address field, and Explorer will activate the appropriate application in the space usually reserved for the HTML content. Notice as well that IE’s help menu merges with Word, producing a composite menu of both applications. See Figure 15.1 for an illustration of this document containment and menu merging.


Figure 15.1  An active document container.

The active document container specification is a variation on the original OLE document container specification. The major difference is that active document server objects must populate the client area of the container application when they become in-place active. Thus, your container adopts the menus and toolbars of the document server and presents them as its own. The details of merging menus and toolbars are not a trivial operation. However, MFC provides a number of classes that make this behavior nearly painless.

Applications that claim active document containment must implement a number of interfaces. These interfaces represent the contract guaranteeing that the necessary functionality is available at such time as the document server requires it.

Storage

A container is responsible for providing a document server with a place, usually a file, to store its contents. In the world of COM, the concept of a container providing a server with a file into which it stores its contents indicates that the container provides an IStorage interface.

The IStorage interface is central to the concept of COM’s structured storage. Structured storage manages data in a hierarchical format within a single physical file. The components of structured storage are storages and streams. Storages can be thought of as directories in a traditional file system. Storages can contain other storages and/or can contain streams. This is akin to a directory containing other directories and/or files. Thus, streams can be thought of as files within a traditional file system. Streams contain an object’s data (see Figure 15.2).


Figure 15.2  Storage and stream relationship.

It should not surprise you that the IStorage interface provides the methods and properties for creating and modifying storage objects. See Table 15.1 for a list and description of the IStorage interface’s member functions.

Table 15.1 The Methods of the IStorage Interface and Their Descriptions

Method Description

CreateStream Creates and opens a stream object in this storage object
OpenStream Opens an existing stream object within this storage object
CreateStorage Creates and opens a new storage object within this storage object
OpenStorage Opens an existing storage object
CopyTo Copies the contents of an open storage object into another storage object
MoveElementTo Copies or moves a storage or stream from this storage object to another storage object
Commit Completes an operation on a transacted storage object
Revert Discards an operation on a transacted storage object
EnumElements Returns an enumerator object of storages and streams in this object
DestroyElement Removes the specified storage or stream from this storage object
RenameElement Renames the specified storage or stream in this storage object
SetElementTimes Sets the various time properties of this storage object
SetClass Allows you to assign a specific CLSID to this storage object
SetStateBits Stores up to 32 bits of state information in this storage object
Stat Retrieves STATSTG for this storage object

You can glean a lot of understanding of this interface by reviewing the methods. There are methods to create, rename, enumerate, and delete objects. These operations correspond nicely to those operations you perform using either a command-line interface or graphical shell of a traditional file system.

The Stat method enables you to retrieve information that is similar to that which you retrieve using the _stat runtime function. The properties that _stat returns reside in the STATSTG structure, which is defined as follows:

typedef struct tagSTATSTG
{
    LPWSTR          pwcsName;
    DWORD           type;
    ULARGE_INTEGER  cbSize;
    FILETIME        mtime;
    FILETIME        ctime;
    FILETIME        atime;
    DWORD           grfMode;
    DWORD           grfLocksSupported;
    CLSID           clsid;
    DWORD           grfStateBits;
    DWORD           reserved;
} STATSTG;

Objects that implement the IStream interface support operations that are similar to those you perform on files. Such operations include reading and writing data, as well as the file pointer type operations you perform with seek. See Table 15.2 for a list and description of the IStream interface’s member functions.



Table 15.2 The Methods of the IStream Interface and Their Descriptions

Method Description

Read Reads the specified number of bytes
Write Writes the specified number of bytes
Seek Changes the seek pointer to a new location relative to the beginning of the stream, the end of the stream, or the current seek pointer
SetSize Changes the size of the stream object
CopyTo Copies a specified number of bytes to another stream
Commit Completes a transaction operation
Revert Cancels a transaction operation
LockRegion Restricts access to a specified range of bytes in the stream
UnlockRegion Removes any access restrictions to a specified range of bytes
Stat Retrieves the STATSTG structure for this stream
Clone Creates a new stream object that references the same bytes as this stream

You must implement the IPersistStorage interface in your container application. This interface allows you to pass an object that derives from IStorage to an active document server. In this manner, a container application can offer persistence to the document servers it contains, without actually knowing anything about the file format or contents. This interface is one of the cornerstones of OLE structured storage. See Table 15.3 for a list and description of the IPersistStorage interface’s member functions.

Table 15.3 The Methods of the IPersistStorage Interface and Their Descriptions

Method Description

IsDirty Indicates whether the object has changed
InitNew Initializes a new storage object
Load Initializes an object from its existing storage
Save Saves an object and any nested objects
SaveCompleted Notifies the object that it can enter a state to accept changes
HandsOffStorage Instructs the object to release all storage objects that have been passed to it by its container

You see that by the interface for IPersistStorage there are vanilla Load and Save methods that are content-independent. As a general rule, a container application does not call these methods directly; instead, it calls OleLoad and OleSave. These functions create uninitialized object instances and call the Commit function where appropriate.

Site Objects

It should not come as a surprise to you that it is important for a document server to be able to obtain information about its container. The IOleClientSite interface defines methods that offer a server just such information. Your container application must create an object of IOleClientSite for each server that it contains. For active document containers, there is only a single instance because the container allows only a single active server at a time. This interface must also be present in ActiveX containers, and an object based on this interface must be available to each control that the container embeds. See Table 15.4 for a list and description of the IOleClientSite interface’s member functions.

Table 15.4 The Methods of the IOleClientSite Interface and Their Descriptions

Method Description

SaveObject Saves embedded object
GetMoniker Requests object’s moniker
GetContainer Requests pointer to object’s container
ShowObject Asks container to display object
OnShowWindow Notifies container when object becomes visible or invisible
RequestNewObjectLayout Asks container to resize display site

Communication between the container and the objects it contains is possible by method invocation on an interface. Responding to asynchronous events requires a different approach. Therefore, the container application must implement IAdviseSink to receive data change notifications, presentation changes, and so on, from document and control server objects. See Table 15.5 for a list and description of the IAdviseSink interface’s member functions.



Table 15.5 The Methods of the IAdviseSink Interface and Their Descriptions

Method Description

OnDataChange Advises that data has changed
OnViewChange Advises that view of object has changed
OnRename Advises that name of object has changed
OnSave Advises that object has been saved to disk
OnClose Advises that object has been closed

In-Place Activation

When it comes to in-place activation, the container must give up a lot of control in order to belong to the active document container club. Activation of server objects leads to an extensive series of user interface changes; your application toolbars and menus will augment with the document server’s menus and toolbars. Implement this interface in the site objects that the container provides for each document server.

Table 15.6 The Methods of the IOleInPlaceSite Interface and Their Descriptions

Method Description

CanInPlaceActivate This function allows the container to process an activation request, returning TRUE if the server can activate.
OnInPlaceActivate Notification to the container that an embedded object is activating.
OnUIActivate Notification to the container that its menu is about to be replaced with a composite menu.
GetWindowContext Enables an in-place object to retrieve window interfaces that form at the window object hierarchy, and the position in the parent window to locate the object’s in-place activation window.
Scroll Specifies the number of pixels the container will scroll the server.
OnUIDeactivate The container can now reinstate the interface it had before the in-place server went active.
OnInPlaceDeactivate The container receives this notification that the in-place object is no longer active.
DiscardUndoState Instructs the container to discard its undo state.
DeactivateAndUndo Deactivate the object and revert to undo state.
OnPosRectChange Object’s width and/or height have changed.

You already know that each document server requires a site object to help manage interaction with the container. There is, however, an integration issue with the container application’s frame window. The frame is responsible for managing menus, keyboard accelerators, the status bar, and so on. For in-place activation to succeed in a visual sense, it must be able to wrest some control over these resources from the container. The IOleInPlaceFrame interface provides methods to achieve these activation objectives. See Table 15.7 for a list and description of the IOleInPlaceFrame interface’s member functions.

Table 15.7 The Methods of the IOleInPlaceFrame Interface and Their Descriptions

Method Description

InsertMenus Allows a container to insert menus into the in-place composite menu
SetMenu Activates the composite menu in the container’s frame
RemoveMenus Allows a container to remove menus from the in-place composite menu
SetStatusText Sets and displays status text
EnableModeless Enables or disables modeless dialog boxes
TranslateAccelerator Translates container-frame accelerator keystrokes

Document Extensions

There are a few differences between traditional OLE document servers and the newer breed of active document servers. You can broadly define these extensions as a means to establish better lines of communication between container and server.

The IOleDocumentSite interface instructs the container to bypass the normal activation sequence and requests activation directly from the document site. See Table 15.8 for a list and description of the IOleDocumentSite interface’s member functions.

Table 15.8 The IOleDocumentSite Interface Method and its Description

Method Description

ActivateMe Activates the server as a document object as opposed to an in-place active object



The IOleCommandTarget interface is extremely helpful because it allows the container and the document server to dispatch commands to each other. With this interface, a container can leave in place its Print, Save, New, and so on, toolbar buttons and menus, allowing the server to respond appropriately. See Table 15.9 for a list and description of the IOleCommandTarget interface’s member functions.

Table 15.9 The Methods of the IOleCommandTarget Interface and Their Descriptions

Method Description

QueryStatus Use this command to identify the supported commands.
Exec Execute the specified command.

The last interface to consider with active document containment is IContinueCallback. Generally, asynchronous event processing is the domain of outgoing interfaces and connection points. Active document containers are given a break with this lightweight callback mechanism for interruptible processes. See Table 15.10 for a list and description of the IContinueCallback interface’s member functions.

Table 15.10 The Methods of the IContinueCallback Interface and Their Descriptions

Method Description

FContinue Identifies whether an operation should continue
FContinuePrinting Identifies whether a printing operation should continue

Building the Simplest Active Document Container

Constructing an active document container is straightforward, and AppWizard offers much help with the generation of skeleton code. The project name is ActiveDocContainer (see Figure 15.3).


Figure 15.3  The ActiveDocContainer project.

This application uses the single document interface (SDI), and it is important to specify Active Document Container support on the third wizard step page (see Figure 15.4).


Figure 15.4  Project options.

Accepting the defaults for the remaining options, AppWizard will proceed to generate code for you, which results in the creation of almost two dozen files.

You end up with the standard application, frame, document, and view files: ActiveDocContainer.cpp, MainFrm.cpp, ActiveDocContainerDoc, and ActiveDocContainerView. In addition, the wizard generates files for an in-place frame and OLE document support: IpFrame.cpp, CntrlItem.cpp, and SrvrItem.cpp.

Compiling this code yields a simple application that supports the embedding of any registered document server. Run the application and insert the active document that you built in Chapter 12, “MFC OLE Servers” (see Figure 15.5).


Figure 15.5  Active document server in container.

OLE Controls

ActiveX controls are probably among the most recognizable COM objects. In introducing the OLE control architecture (the original name for ActiveX controls), Microsoft offers Visual C++ developers a taste of the component good life, which Visual Basic developers had enjoyed for a while with VBXs. Although the VBX architecture was not COM-based, it offered Visual Basic developers to leverage third-party components in a visual development environment. Unfortunately, VBXs were not available to the Visual C++ development environment. This probably turned out for the best, because in developing a COM strategy, Microsoft came up with a platform-independent, language-neutral replacement—OLE controls.

Visual C++ is quite adept at helping you build applications that host ActiveX controls. The development environment provides you almost the same level of graphical application development that you find in Visual Basic. The term control is somewhat misleading, though. When you think of Windows controls, you probably envision list boxes and combo boxes. Although ActiveX controls can have visual representation and behave in a manner similar to these dialog controls, they don’t have to. In fact, many controls forego a runtime visual interface and offer services that you do not have to write otherwise. Such services can include compression algorithms or communication protocols.

There is an AppWizard option for supporting ActiveX controls at project creation time, but adding ActiveX control support to an existing project is not difficult.

Adding Containment to an Existing Project

The AppWizard provides an ever-increasing number of options that influence the initial skeleton code generation. In many cases, ignoring an option, or selecting the wrong option, results in code that is difficult at best to massage into a state that offers a compatible solution. If you didn’t select ActiveX containment during project creation, you are not far from where you want to be. Essentially, you just add a single function call and include the appropriate header file:

#include <Afxdisp.h>

BOOL CContainerApp::InitInstance()
{
    AfxEnableControlContainer();
}

ActiveX Container

This section focuses on static containment of ActiveX controls. Different from the active document container that allows you to select the document server to embed at runtime, the containers you develop for ActiveX controls are “wired” for specific controls. There are many similarities between automation controllers and ActiveX control containers. ActiveX controls employ IDispatch communication for properties and methods, in a manner identical to automation. Additionally, you use the same MFC functions (SetProperty, GetProperty, InvokeHelper) to control the ActiveX component.

The similarities end with the actual class that wraps the automation server of active control. Whereas in an automation controller the COleDispatchDriver class is the base class, control containers use the CWnd class.

class IDrive : public COleDispatchDriver
{
public:
    IDrive();
    virtual ~IDrive();

    // interface method
    void TrackInformation(short nIndex, BSTR * bstrTitle);

    // property methods
    short GetAlbumLength();
        void  PutAlbumLength(short nLength);
};

class CMFCActiveX : public CWnd
{
protected:
    DECLARE_DYNCREATE(CMFCActiveX)
public:
// Constructors

// Attributes (properties)
public:
    BOOL GetAcquireData();
    void SetAcquireData(BOOL);

// Operations (methods)
public:
    SCODE SetAntenna(short Antenna);
    void AboutBox();
};



You might have a hard time believing that the lowly CWnd class has anything to do with OLE. However, it supports a number of control containment functions that you never took notice of.

Method Description

SetProperty Sets an OLE control property
OnAmbientProperty Implements ambient property values
GetControlUnknown Retrieves a pointer to an unknown OLE control
GetProperty Retrieves an OLE control property
InvokeHelper Invokes an OLE control method or property

Two methods that are not found in COleDispatchDriver are GetControlUnknown and OnAmbientProperty.

Just as a number of wizards and helper dialogs are available to you for automation controller construction, so too are a number of tools that make it easy to incorporate ActiveX controls into your application. There are a number of ways to use ActiveX controls in a project. The first approach that you will see involves constructing a proxy based on the CWnd class. You then instantiate an object of this type and add it manually to a window or dialog. The second approach is more interactive, where you add the control directly to a dialog, in the same manner that you add edit controls and list boxes.

Create a project whose name is ControlProxy and which implements a Single Document Interface (SDI) (see Figure 15.6). Accept all the other defaults that AppWizard supplies. Step 3 of this process includes ActiveX support.


Figure 15.6  The ControlProxy project.

The next step is to identify the control you want to incorporate into your project. Visual C++ provides a tool to select an ActiveX component and construct a proxy class. The tool that supports this operation has gone through changes with almost each release of Visual C++ because support for ActiveX controls was introduced. Open the Components and Controls Gallery by selecting Add To Project from the Project menu (see Figure 15.7). This will produce a submenu with a Components and Controls selection.


Figure 15.7  The Add To Project menu command.

The dialog, which is really just a glorified File Open dialog, contains two subdirectories: Registered ActiveX Controls and Visual C++ Components. These directories reside below your Visual Studio install directory and contain shortcuts to the appropriate items (see Figure 15.8).


Figure 15.8  The Components and Controls Gallery dialog.

Select the MFCActiveX control that you developed in the previous chapter and click Insert. A dialog box will appear with a list of incoming interfaces from which you select the ones for which the tool will generate proxy code (see Figure 15.9).


Figure 15.9  The Confirm Classes dialog.

Incoming interfaces are those that expose methods and properties. The incoming interface for the MFCActiveX component offers two properties and a single method.

    [ uuid(D33E5F34-CC5B-11D2-8FBA-00105A5D8D6C),
    helpstring(“Dispatch interface for MFCActiveX Control”), hidden ]
    dispinterface _DMFCActiveX
    {
        properties:
         // NOTE - ClassWizard will maintain property information here.
            //    Use extreme caution when editing this section.
            //{{AFX_ODL_PROP(CMFCActiveXCtrl)
            [id(1)] boolean AcquireData;
            [id(2)] short BufferSize;
            //}}AFX_ODL_PROP

        methods:
         // NOTE - ClassWizard will maintain method information here.
            //    Use extreme caution when editing this section.
            //{{AFX_ODL_METHOD(CMFCActiveXCtrl)
            [id(3)] SCODE SetAntenna(short Antenna);
            //}}AFX_ODL_METHOD

            [id(DISPID_ABOUTBOX)] void AboutBox();
    }

The properties, which you are familiar with, are AcquireData and BufferSize. The single method is SetAntenna. This interface appears in the coclass definition as an incoming interface whose name is _DMFCActiveX.

    [ uuid(D33E5F36-CC5B-11D2-8FBA-00105A5D8D6C),
      helpstring(“MFCActiveX Control”), control ]
    coclass MFCActiveX
    {
        [default] dispinterface _DMFCActiveX;
        [default, source] dispinterface _DMFCActiveXEvents;
    };

In addition, the Confirm Classes dialog allows you to override the default declaration and implementation file names. Dismissing the dialog causes Visual C++ to generate the following class definition:

/////////////////////////////////////////////////////////////////////
// CMFCActiveX wrapper class

class CMFCActiveX : public CWnd
{
protected:
    DECLARE_DYNCREATE(CMFCActiveX)
public:
    CLSID const& GetClsid()
    {
        static CLSID const clsid
            = { 0xd33e5f36, 0xcc5b, 0x11d2,
              { 0x8f, 0xba, 0x0, 0x10, 0x5a, 0x5d, 0x8d, 0x6c } };
        return clsid;
    }
    virtual BOOL Create(LPCTSTR lpszClassName,
        LPCTSTR lpszWindowName, DWORD dwStyle,
        const RECT& rect,
        CWnd* pParentWnd, UINT nID,
        CCreateContext* pContext = NULL)
    { return CreateControl(GetClsid(), lpszWindowName,
                           dwStyle, rect, pParentWnd, nID); }
    BOOL Create(LPCTSTR lpszWindowName, DWORD dwStyle,
        const RECT& rect, CWnd* pParentWnd, UINT nID,
        CFile* pPersist = NULL, BOOL bStorage = FALSE,
        BSTR bstrLicKey = NULL)
    { return CreateControl(GetClsid(), lpszWindowName,
                           dwStyle, rect, pParentWnd, nID,
        pPersist, bStorage, bstrLicKey); }

// Attributes
public:
    BOOL GetAcquireData();
    void SetAcquireData(BOOL);
    short GetBufferSize();
    void SetBufferSize(short);

// Operations
public:
    SCODE SetAntenna(short Antenna);
    void AboutBox();
};

The class contains sections that define the methods and properties as well as generating the code necessary to construct a control. The CWnd class defines a virtual Create function. The wrapper overrides this function and calls CreateControl with the appropriate CLSID. Thus, all you have to do to display the control is create an instance of the object and then call the Create function.



The implementation code for each of the properties uses the CWnd GetProperty and SetProperty functions. These functions simply require a DispID of the property to access, the variant type identifier constant, and either the value to set or a variable of the proper datatype to hold the value from an accessor.

/////////////////////////////////////////////////////////////////////
// CMFCActiveX properties

BOOL CMFCActiveX::GetAcquireData()
{
    BOOL result;
    GetProperty(0x1, VT_BOOL, (void*)&result);
    return result;
}

void CMFCActiveX::SetAcquireData(BOOL propVal)
{
    SetProperty(0x1, VT_BOOL, propVal);
}

short CMFCActiveX::GetBufferSize()
{
    short result;
    GetProperty(0x2, VT_I2, (void*)&result);
    return result;
}

void CMFCActiveX::SetBufferSize(short propVal)
{
    SetProperty(0x2, VT_I2, propVal);
}

The single method relies on InvokeHelper to call a method on the ActiveX control. The InvokeHelper function is identical to the one that you use when invoking methods on an automation server.

/////////////////////////////////////////////////////////////////////
// CMFCActiveX operations
SCODE CMFCActiveX::SetAntenna(short Antenna)
{
    SCODE result;
    static BYTE parms[] =
        VTS_I2;
    InvokeHelper(0x3, DISPATCH_METHOD, VT_ERROR,
                 (void*)&result, parms,
        Antenna);
    return result;
}

Now it is time to put the control to use. You will add code to the Create function of the CControlProxyView object. The class does not provide a handler for the WM_CREATE message by default so you must add one. Display the context menu for the CControlProxyView class in the ClassView pane of the Workspace window and select Add Windows Message Handler (see Figure 15.10).

Locate the WM_CREATE message and select Add and Edit (see Figure 15.11).


Figure 15.10  The ClassView context menu.


Figure 15.11  Windows message handler for WM_CREATE.

This operation generates code that calls the CView base class, returning a -1 value if there is a problem creating the window.

int CControlProxyView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CView::OnCreate(lpCreateStruct) == -1)
        return -1;
    // TODO: Add your specialized creation code here

    return 0;
}

Before you add code to create the ActiveX control, you will add a member variable to the CControlProxyView header file to contain the object. Display the context menu for this class again and select Add Member Variable. The variable type is CMFCActiveX; give it the name mycontrol and allow public access (see Figure 15.12).


Figure 15.12  The Add Member Variable dialog.

Now you must include the CControlProxy header before the ControlProxyView header or there will be no class definition for the compiler.

#include “stdafx.h”
#include “ControlProxy.h”

#include “ControlProxyDoc.h”
#include “MFCActiveX.h”
#include “ControlProxyView.h”

The final steps are to create the control and add it to the view window.

int CControlProxyView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CView::OnCreate(lpCreateStruct) == -1)
        return -1;

    CRect rect(10,10,150,70);
    mycontrol.Create(NULL, WS_VISIBLE, rect, this, 123);

    return 0;
}

You create the ActiveX control, oddly enough, using the Create function. The arguments to this function include the text appearing in the control (or NULL), various window style bits, the encompassing rectangle, the parent window, and a control ID.

You can run the program and see that it is indeed attached to the view window (see Figure 15.13). You can make additional method and property calls on the mycontrol class; however, you are missing event support. You can build the event map manually, but there is an easier way, as you will see in the next example. It is possible to put a wizard to work and have the event handlers stubbed for you.


Figure 15.13  The MFCActiveX control in ControlProxy view.

Control Containment and Events

In this next example, you will see how easy it is to add an ActiveX control to a dialog and use Visual C++ to aid in the creation of event handlers. This project’s name is ControlDialog, and it is a dialog-based application (see Figure 15.14). The ActiveX Controls option is selected by default, so you can select the Finish option after you make the application type selection in step 2.


Figure 15.14  The ControlDialog project.

As soon as AppWizard is done generating code, it will display a dialog on which you begin placing controls. You can delete the static text control that says “TODO: Place dialog controls here.” The first thing you will want to do is place the ActiveX control in the dialog. You can easily accomplish this by right-clicking over the dialog to display the context menu and selecting the Insert ActiveX Control menu command (see Figure 15.15).


Figure 15.15  The dialog context menu.

Locate the MFCActiveX Control entry in the list box and select OK (see Figure 15.16).


Figure 15.16  Inserting an ActiveX control.

You can proceed to add additional controls for the purpose of activating the radar mapping component and selecting an antenna. Upon completion, your dialog might look like Figure 15.17.


Figure 15.17  The final dialog layout.

A check box will turn the system on and off. There are three possible antennas to select from. Three radio buttons represent these settings. The control fires events that represent the data feed and the onboard thermal conditions. The data feed values are added to a list box, and the thermal reading appears in a non-editable edit control.

The next step is to “wire” the dialog components together. Before you can make calls to and accept events from the MFCActiveX control, you must add it to your project. You can accomplish this by displaying ClassWizard and adding a member variable for the control (see Figure 15.18).


Figure 15.18  The ClassWizard Member Variables tab.

Select the control ID of the ActiveX control IDC_MFCACTIVEXCTRL1 and select Add Variable. Visual C++ responds with a dialog box indicating that the control is not in the project and it will generate a wrapper class for you. Selecting OK will cause the Confirm Classes dialog to appear (see Figure 15.19).


Figure 15.19  The Confirm Classes dialog.

This is the same dialog that you used in the previous project. Accept the defaults and select OK. The Add Member Variable dialog will appear (see Figure 15.20).


Figure 15.20  The Add Member Variable dialog.

You can name the variable m_control and select OK. Dismissing the ClassWizard dialog, you will see that a CMFCActiveX class is now part of the project. This class is a wrapper for the control, whose base class is CWnd and is identical to the class you used in the previous example.

class CMFCActiveX : public CWnd
{
protected:
    DECLARE_DYNCREATE(CMFCActiveX)
public:
    CLSID const& GetClsid()
    {
        static CLSID const clsid
            = { 0xd33e5f36, 0xcc5b, 0x11d2,
{ 0x8f, 0xba, 0x0, 0x10, 0x5a, 0x5d, 0x8d, 0x6c } };
        return clsid;
    }
    virtual BOOL Create(LPCTSTR lpszClassName,
        LPCTSTR lpszWindowName, DWORD dwStyle,
        const RECT& rect,
        CWnd* pParentWnd, UINT nID,
        CCreateContext* pContext = NULL)
    { return CreateControl(GetClsid(), lpszWindowName,
                       dwStyle, rect, pParentWnd, nID); }

    BOOL Create(LPCTSTR lpszWindowName, DWORD dwStyle,
        const RECT& rect, CWnd* pParentWnd, UINT nID,
        CFile* pPersist = NULL, BOOL bStorage = FALSE,
        BSTR bstrLicKey = NULL)
    { return CreateControl(GetClsid(), lpszWindowName,
                       dwStyle, rect, pParentWnd, nID,
        pPersist, bStorage, bstrLicKey); }

// Attributes
public:
    BOOL GetAcquireData();
    void SetAcquireData(BOOL);
    short GetBufferSize();
    void SetBufferSize(short);

// Operations
public:
    SCODE SetAntenna(short Antenna);
    void AboutBox();
};



The final step is to respond to events from the ActiveX control. This control provides two events: DataStream and ThermalValue. You can establish handlers for these events by displaying the context menu for the ControlDialog in the resource editor and selecting the Events menu (see Figure 15.21).


Figure 15.21  The ClassView context menu with events.

You are familiar with this dialog; it is the one you use to create handlers for window messages. You will notice, however, that the Class or Object to Handle list box contains the control ID for the ActiveX control (see Figure 15.22). Selecting this entry displays the two events this control supports in the list box to the left.


Figure 15.22  The event handler for the MFCActiveX control.

Adding handlers for both of these events results in code that generates a sink map and defines the appropriate entries.

BEGIN_EVENTSINK_MAP(CControlDialogDlg, CDialog)
    //{{AFX_EVENTSINK_MAP(CControlDialogDlg)
    ON_EVENT(CControlDialogDlg, IDC_MFCACTIVEXCTRL1,
1 /* DataStream */, OnDataStreamMfcactivexctrl1, VTS_BSTR)
    ON_EVENT(CControlDialogDlg, IDC_MFCACTIVEXCTRL1,
2 /* ThermalValue */, OnThermalValueMfcactivexctrl1, VTS_I2)
    //}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()

void CControlDialogDlg::OnDataStreamMfcactivexctrl1(LPCTSTR Control)
{
    // TODO: Add your control notification handler code here
}

void CControlDialogDlg::OnThermalValueMfcactivexctrl1(short Indicator)
{
    // TODO: Add your control notification handler code here
}

All you have to do now is add code to update the dialog box as a result of calls made to these functions by the ActiveX control. You can use the Power System check box to interface with the AcquireData property and identify the SetAntenna value by the contents of the radio buttons.

void CControlDialogDlg::OnPower()
{
    if (m_checkPower.GetCheck())
        m_control.SetAcquireData(true);
    else
        m_control.SetAcquireData(false);
}

void CControlDialogDlg::OnHiRadio1()
{
    m_control.SetAntenna(1);
}

void CControlDialogDlg::OnMedRadio2()
{
    m_control.SetAntenna(2);
}

void CControlDialogDlg::OnLoRadio3()
{
    m_control.SetAntenna(3);
}

The MFCActiveX control’s background will change color to identify the thermal condition inside the module. The control also supports an event that identifies changes to temperature changes. Status text is placed in an edit control, indicating the comfort level of this subsystem. Notice that when the system gets too hot, the component stops acquiring data. A call is made to the check box UI component to remove the check from the box, keeping the dialog in-sync with the control.

void CControlDialogDlg::OnThermalValueMfcactivexctrl1(short Indicator)
{
    switch(Indicator)
    {
    case 1:
        m_editThermal.SetWindowText(“Overload”);
        m_checkPower.SetCheck(false);
        break;
    case 2:
        m_editThermal.SetWindowText(“Uncomfortable”);
        break;
    case 3:
        m_editThermal.SetWindowText(“Normal”);
        break;
    }
}

Finally, you respond to the data stream event of the control by adding a string to the list box. One thing you might want to double-check is that you do not have the LBS_SORT bit set, or the data will not be added to the control in chronological order.

void CControlDialogDlg::OnDataStreamMfcactivexctrl1(LPCTSTR Control)
{
    m_listData.AddString(Control);
}

When you run the program, you will have a simple control panel that allows you to switch the radar mapping subsystem on and off and select the gain of the antenna (see Figure 15.23).


Figure 15.23  The radar mapping component in use.

Summary

Containers are a powerful concept in OLE. In this chapter, you created applications that demonstrate both active document containers and ActiveX control containers. Although the purpose for creating a container of one type or the other is a function of the job you are trying to accomplish, many of the concepts you learn for one can easily be applied to your understanding of another.